package abstracts;

import interfaces.Collidable;
import interfaces.CollisionHandler;
import interfaces.Controllable;
import interfaces.Movable;
import interfaces.Visible;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.util.Collections;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.LinkedList;
import tools.CollisionGrid;
import tools.FrameLimit;
import tools.GameApplet;
import tools.Statistics;

/** All levels must extend this class. */
public abstract class Level extends Screen {
    /** */
    public static final int WIDTH = 768;
    /** */
    public static final int HEIGHT = 576;
    /** */
    public static final int GRIDSIZE = 32;

    /** */
    private final Set<Controllable> controllables;
    /** */
    private final Set<Movable> movables;
    /** */
    private final Set<CollisionHandler> collisionHandlers;
    /** */
    private final List<Visible> visibles;
    /** */
    private final CollisionGrid collisionGrid;

    /**
     * @param gameApplet - the {@link GameApplet} which this level is
     *            to be displayed in.
     */
    public Level(final GameApplet gameApplet) {
        super(gameApplet);

        controllables = new HashSet<Controllable>();
        movables = new HashSet<Movable>();
        collisionHandlers = new HashSet<CollisionHandler>();
        visibles = new LinkedList<Visible>();
        collisionGrid = new CollisionGrid(HEIGHT / GRIDSIZE, WIDTH / GRIDSIZE);
    }

    @Override
    public final int getWidth() {
        return WIDTH;
    }

    @Override
    public final int getHeight() {
        return HEIGHT;
    }

    /** @return the {@linkplain CollisionGrid}. */
    public final CollisionGrid getCollisionGrid() {
        return collisionGrid;
    }

    @Override
    public final synchronized void begin() {
        Statistics.reset();
        load();
    }

    /** Called by begin. */
    public void load() {
        // Do nothing
    }

    /** Cleans up all references to game objects held by the level. */
    @Override
    public final synchronized void end() {
        free();
        clear();
    }

    /** Called by end. */
    public void free() {
        // Do nothing
    }

    /**
     * Calls the control, move and collide methods of the appropriate
     * objects. Then puts the thread to sleep until the next step.
     */
    @Override
    public final synchronized void step() {
        // Handle input
        for (Controllable obj : new HashSet<Controllable>(controllables)) {
            if (obj != null) {
                obj.control();
            }
        }

        // Move all movable game objects
        for (Movable obj : new HashSet<Movable>(movables)) {
            if (obj != null) {
                obj.move();
            }
        }

        // Handle all collisions
        for (CollisionHandler obj : new HashSet<CollisionHandler>(
                collisionHandlers)) {
            if (obj != null) {
                obj.collide(collisionGrid);
            }
        }
    }

    /**
     * Calls the draw method of the appropriate objects, ordered by
     * their depth.
     * 
     * @param g - the {@linkplain Graphics} to draw everything to.
     */
    @Override
    public final synchronized void draw(final Graphics g) {
        // Sort visible objects by depth
        Collections.sort(visibles, Visible.DEPTH_COMPARATOR);

        // Draw all visible game objects
        for (Visible obj : visibles) {
            if (obj != null) {
                obj.draw(g);
            }
        }

        // TODO Make the HUD a separate object.
        // Draw the HUD
        g.setColor(Color.WHITE);
        g.setFont(new Font("Georgia", Font.BOLD, 12));
        g.drawString("Fps: " + getCurrentFps() + " / 60", 16, 16);
        g.drawString("Treasure: " + Statistics.treasure, 16, 32);
    }

    /** Restarts the level. */
    public final void restart() {
        end();
        begin();
    }

    /**
     * Adds appropriate references to the object.
     * 
     * @param object - the {@linkplain Object}.
     */
    public final synchronized void add(final Object object) {
        if (object instanceof Controllable) {
            controllables.add((Controllable) object);
        }

        if (object instanceof Movable) {
            movables.add((Movable) object);
        }

        if (object instanceof CollisionHandler) {
            collisionHandlers.add((CollisionHandler) object);
        }

        if (object instanceof Visible) {
            visibles.add((Visible) object);
        }

        if (object instanceof Collidable) {
            collisionGrid.addCollidable((Collidable) object);
        }
    }

    /**
     * Removes all references to the object.
     * 
     * @param object - the {@linkplain Object}.
     */
    public final synchronized void remove(final Object object) {
        movables.remove(object);
        controllables.remove(object);
        collisionHandlers.remove(object);
        visibles.remove(object);

        if (object instanceof Collidable) {
            collisionGrid.removeCollidable((Collidable) object);
        }
    }

    /** Removes all references to all objects. */
    public final synchronized void clear() {
        controllables.clear();
        movables.clear();
        collisionHandlers.clear();
        visibles.clear();
        collisionGrid.clear();
    }
}
